package lab.cadl.lirui.markdown.core.parser

import lab.cadl.lirui.markdown.core.model.*
import lab.cadl.lirui.markdown.core.utils.ImageCache
import lab.cadl.lirui.markdown.core.utils.ImageCacheImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.io.InputStream
import java.nio.file.Paths
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*

interface DocumentParser {
    fun parse(file: File): MarkdownDocument
}

interface DocumentLinesParser {
    fun parse(lines: Iterator<String>, state: ParseState)
}

interface ParseState {
    val activeSection: Section
    val inlineProcessor: InlineProcessor
    val imageCache: ImageCache
    val cellContentParser: DocumentLinesParser
    val paperRefDatabase: PaperRefDatabase
    val document: MarkdownDocument
    fun createChildProcessor(): DocumentParser
    fun appendTopLevelSection(section: Section): Section
    fun createSection(level: Int, title: String): Section
    fun resolveRelativePath(relativePath: String): String
}

class MarkdownParserFactory(private val imageCache: ImageCache) {
    private val processorList: MutableList<BlockProcessor> = mutableListOf()
    private val cellProcessorList: MutableList<BlockProcessor> = mutableListOf()

    init {
        registerProcessor(HeadingProcessor())
        registerProcessor(ParagraphProcessor())
        registerProcessor(BlockImageProcessor())
        registerProcessor(FencedCodeProcessor())
        registerProcessor(TableBlockProcessor())
        registerProcessor(StandardTableProcessor())
        registerProcessor(ListBlockProcessor())
        registerProcessor(BlockQuoteProcessor())
        registerProcessor(FormulaBlockProcessor())
        registerProcessor(RefSourceProcessor())
        registerProcessor(RefShowProcessor())
        registerProcessor(KeyWordProcessor())
        registerProcessor(IncludeBlockProcessor())
        registerProcessor(TocBlockProcessor())
        registerProcessor(ThematicBreakProcessor())
        registerProcessor(PropBlockProcessor())
        registerProcessor(TitlePropProcessor())
        registerProcessor(SubTitlePropProcessor())
        registerProcessor(VariableBlockProcessor())

        registerTableCellProcessor(ListBlockProcessor())
        registerTableCellProcessor(ParagraphProcessor())
    }

    fun registerProcessor(processor: BlockProcessor): MarkdownParserFactory {
        processorList.add(processor)
        processorList.sortByDescending { it.priority }
        return this
    }

    fun registerTableCellProcessor(processor: BlockProcessor): MarkdownParserFactory {
        cellProcessorList.add(processor)
        cellProcessorList.sortByDescending { it.priority }
        return this
    }

    fun create(): MarkdownParser {
        return MarkdownParser(processorList.toList(), imageCache,
                DocumentLinesParserImpl(cellProcessorList.toList()),
                PaperRefDatabase())
    }
}

class DocumentLinesParserImpl(private val processorList: List<BlockProcessor>) : DocumentLinesParser {
    override fun parse(lines: Iterator<String>, state: ParseState) {
        var block = mutableListOf<String>()


        while (lines.hasNext()) {
            val line = lines.next()

            if (line.isBlank()) {
                if (block.size > 0) {
                    block = processBlock(block, state).toMutableList()

                    if (!block.isEmpty()) {
                        block.add(line)
                    }
                }
            } else {
                block.add(line)
            }
        }

        if (block.size > 0) {
            processBlock(block, state)
        }
    }

    private fun processBlock(block: List<String>, state: ParseState): List<String> {
        var remainBlock = block
        while (!remainBlock.isEmpty()) {
            val count = remainBlock.size

            for (processor in processorList) {
                val result = processor.test(remainBlock, state)
                if (result.accept) {
                    processor.process(result.block, state)
                    remainBlock = result.remain
                    break
                } else if (result.state == TestState.NEED_MORE) {
                    return remainBlock
                }
            }

            assert(remainBlock.size != count)
        }

        return emptyList()
    }
}

class MarkdownParser(processorList: List<BlockProcessor>,
                     override val imageCache: ImageCache,
                     override val cellContentParser: DocumentLinesParser,
                     override val paperRefDatabase: PaperRefDatabase) : ParseState, DocumentParser {
    companion object {
        val LOG: Logger = LoggerFactory.getLogger(MarkdownParser::class.java)
    }

    override val document = MarkdownDocument(paperRefDatabase)
    private val sectionStack: MutableList<Section> = mutableListOf(document)
    private val lineParser = DocumentLinesParserImpl(processorList)
    private var baseDir: String = "./"
    private var standaloneSection: Section? = null

    override fun parse(file: File): MarkdownDocument {
        val doc = parse(file.inputStream(), file.parent?.toString() ?: "./")
        if (!doc.hasProp(MarkdownConstants.TitleProperty)) {
            doc.prop(MarkdownConstants.TitleProperty, file.nameWithoutExtension)
        }

        if (!doc.hasProp(MarkdownConstants.SubTitleProperty)) {
            doc.prop(MarkdownConstants.SubTitleProperty, DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
                    .withLocale(Locale.CHINA)
                    .withZone(ZoneId.systemDefault())
                    .format(Instant.now()))
        }

        return doc
    }

    fun parse(inputStream: InputStream, baseDir: String): MarkdownDocument {
        this.baseDir = baseDir

        inputStream.bufferedReader().use {
            lineParser.parse(it.lines().iterator(), this)
        }

        collectRef()
        return document
    }

    override fun createChildProcessor(): DocumentParser {
        val parentImageCache = imageCache
        class ChildParser : DocumentParser by this, ParseState by this {
            private var baseDir: String = "./"

            override var imageCache: ImageCache = parentImageCache

            override fun parse(file: File): MarkdownDocument {
                return parse(file.inputStream(), file.parent?.toString() ?: "./")
            }

            fun parse(inputStream: InputStream, baseDir: String): MarkdownDocument {
                this.baseDir = baseDir
                this.imageCache = imageCache.createDelegation(baseDir)

                inputStream.bufferedReader().use {
                    lineParser.parse(it.lines().iterator(), this)
                }

                collectRef()
                return document
            }

            override fun resolveRelativePath(relativePath: String): String {
                return Paths.get(baseDir, relativePath).toString()
            }
        }

        return ChildParser()
    }

    private fun collectRef() {
        handleChildren(document)
    }

    private fun handleChildren(element: Element) {
        for (child in element.children) {
            if (child is RefTarget) {
                LOG.info("ADD REF {}", child)
                document.addTarget(child)
            }

            handleChildren(child)
        }
    }

    override val activeSection: Section
        get() {
            return standaloneSection ?: sectionStack.last()
        }

    override val inlineProcessor: InlineProcessor = InlineProcessorImpl()

    override fun createSection(level: Int, title: String): Section {
        standaloneSection = null

        var index = 0
        while (index < sectionStack.size && sectionStack[index].level < level) {
            index += 1
        }

        return when {
            index == sectionStack.size -> {
                val section = Section(activeSection)
                section.heading = Heading(parent = section, level = level, seq = 1, title = title)
                sectionStack.add(section)
                section
            }
            level == sectionStack[index].level -> {
                val prev = sectionStack[index]
                val section = Section(parent = prev.parentSection)
                section.heading = Heading(parent = section, level = level, seq = prev.seq + 1, title = title)
                sectionStack.removeAll(sectionStack.subList(index, sectionStack.size))
                sectionStack.add(section)
                section
            }
            else -> {
                val parent = sectionStack[index - 1]
                val section = Section(parent = parent)
                section.heading = Heading(parent = section, level = level, seq = parent.subSections.size + 1, title = title)
                sectionStack.removeAll(sectionStack.subList(index + 1, sectionStack.size))
                sectionStack.add(section)
                section
            }
        }
    }

    override fun appendTopLevelSection(section: Section): Section {
        standaloneSection = section
        document.appendChild(section)
        return section
    }

    override fun resolveRelativePath(relativePath: String): String {
        return Paths.get(baseDir, relativePath).toString()
    }
}